/* pjd.c - implementação vazia do protocolo de janela deslizante
 * Não implementa nada, deve ser apenas suficiente para um teste otimista
 * dos protocolos de aplicação na ausência de erros ou de múltiplas
 * transmissões concorrentes.
 * ******* Não há garantia de que isso funcione sempre *********
 */

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>
#include <cmath>

#include "pjd.h"
#include "tp_socket.h"

#include <sys/time.h>
#include<sys/resource.h>

#define FIRST_MSG "CONNECT"
#define LAST_MSG "\4BYE"

int windowPackage::dataMaxSize=0;
int pjdConnection::nextPjdId = 0;
pthread_t sendThread, receiveThread;
pthread_mutex_t recvMutBuffer;
pthread_mutex_t windMutBuffer;

//Tamanho definido das estruturas
#define WINDOW_SIZE 10
#define BUFFER_SIZE 50
//Tempos de espera
#define WAIT_WHILE_EMPTY 0.1
#define WAIT_WHILE_FULL 0.1
#define WAIT_WHILE_INCOMPLETE 0.1
#define WAIT_RECV 1.0
#define TIME_TO_RESEND 0.6
#define PACKAGE_FULL_SIZE 1052 // 1024 + 28 extensoes....

//Variaveis de controle
int messageNumber = 1;
int steamControl = BUFFER_SIZE;
list windowBuffer;
list windowRecvBuffer;
char msgRecvd[PACKAGE_FULL_SIZE];
list activeConnections;
windowPackage** window; //Janela global... received e sender vao acessar!!
list pjdWindow;
int messageRecvdNumber=1;
int    pjd_socket;           /* socket usado na comunicação */
struct sockaddr_in other_end;

int active_end = 0;

//Variaveis para os dados finais da transferencia
time_t startTransferTime;
time_t endTransferTime;
int TotalBlockSent = 0;
int TotalBlockReceived = 0;
int TotalAckReceived = 0;
int TotalBlockResent = 0;
int TotalBlockDuplicate = 0;
int TotalBytes = 0;


void error(std::string errorMsg){
	std::cout<< errorMsg << "\n";
	exit(-1);
}

/**
 * Global, pq a window foi definida global tbm
 * Função utilizada no pjd_close que verifica se a janela
 * foi toda enviada antes de fechar a conexão
 */
bool windowIsEmpty(){

	for(int i = 0; i < WINDOW_SIZE; i++){
		if(window[i] != NULL)
			return false;
	}

	return true;
}


/* Função que imprime os dados relevantes da transmissão
 * Como blocos enviados e recebidos e tempo gasto
 **/
void TransferResume(){

	printf("-- Resumo da transferência: \n");
	double diffTime = difftime(endTransferTime, startTransferTime);
	printf("Tempo total da transferência: %.2lf segundos \n", diffTime);
	printf("Total de blocos enviados: %d  \n", TotalBlockSent);
	printf("Total de blocos re-enviados: %d  \n", TotalBlockResent);
	printf("Total de blocos recebidos: %d  \n", TotalBlockReceived);
	printf("Total de confirmações recebidas: %d  \n", TotalAckReceived);
	printf("Total de blocos duplicados recebidos: %d  \n", TotalBlockDuplicate); //Aqui vai ter ack's duplicados ???
	int tax = TotalBlockSent / diffTime;
	printf("Taxa média de envio de blocos por segundo: %d  \n", tax); //Aqui vai ter ack's duplicados ???

}


/*
 * Função responsável pelo recebimento de pacotes.
 * Quando recebe faz a verificação de bits, verificação se
 * pertence a janela e então inclui o pacote recebido no buffer
 * de entrada. Cria-se em seguida o pacote de confirmação e insere no
 * buffer de saída.Para controle da janela vamos definir que toda
 * mensagem começa com o blockNumber = 0; E a numeração da mensagem
 * começa a partir de 1.
 */

void *receivePackageThread(void* notUsed){


	int recvSize;
	char recvMessage[PACKAGE_FULL_SIZE];

	while(true){

		recvSize = tp_recvfrom(pjd_socket , recvMessage, PACKAGE_FULL_SIZE, &other_end); //Função de recebimento
		TotalBlockReceived++;
		TotalBytes += recvSize;

		bool verified = windowPackage::verifyPackage(recvMessage, recvSize); //Verificação de bits

		if(verified){


//			printf("--Pacote recebido --\n");
			windowPackage *package = windowPackage::mountPackageReceived(recvMessage, recvSize); //Transforma o pacote recebido em objeto
//			printf("MsgId= %d, BlockN = %d\n", package->messageId, package->blockNumber);


			//Verificação da janela
			if((!windowRecvBuffer.exists(package->messageId, package->blockNumber))  && (package->messageId >= messageRecvdNumber || messageRecvdNumber - package->messageId > 40 )){
				if(package->op == 2){ //OP = 2 == pacote de ACK... se ACK, pacote esta na janela do sender!!! ou eh descartado...
					TotalAckReceived++;
					for(int i=0; i< WINDOW_SIZE; i++){
						if(window[i]!=NULL){
							if((window[i]->blockNumber == package->blockNumber) && (window[i]->messageId == package->messageId))
								window[i]->ack = true; //Pacote confirmado
						}
					}
					steamControl = package->dataLen; //Controle do fluxo do receptor
				}

				if(package->op == 1 || package->op==4){ // OP = 1 == pacote de data..

					if(windowRecvBuffer.getSize() < BUFFER_SIZE){ //Verificando espaço no buffer
						pthread_mutex_lock(&recvMutBuffer);
						windowRecvBuffer.insertNode(*package); //Insere o pacote recebido no buffer de entrada
						pthread_mutex_unlock(&recvMutBuffer);
						windowPackage *ackPackage = new windowPackage(); //Criando bloco de confirmação
						ackPackage->op = 2;
						ackPackage->blockNumber = package->blockNumber;
						ackPackage->messageId = package->messageId;
						ackPackage->dataLen = BUFFER_SIZE - windowRecvBuffer.getSize(); // Manda para o receptor a quantidade restante de buffer

						pthread_mutex_lock(&windMutBuffer);
						windowBuffer.insertNode(*ackPackage); //Insere a confirmação no buffer de saida
						pthread_mutex_unlock(&windMutBuffer);
					}
				}
			}
			else{ //Pacote ja recebido --> reenvia a confirmação
				if(package->op == 1){
					windowPackage *ackPackage = new windowPackage();
					ackPackage->op = 2;
					ackPackage->blockNumber = package->blockNumber;
					ackPackage->messageId = package->messageId;
					ackPackage->dataLen = BUFFER_SIZE - windowRecvBuffer.getSize(); // Manda para o receptor a quantidade restante de buffer

					pthread_mutex_lock(&windMutBuffer);
					windowBuffer.insertNode(*ackPackage);
					pthread_mutex_unlock(&windMutBuffer);
				}

			}
		}
	}
}


/**
 * Função para a thread de envio de pacotes.
 * Basicamente envia do buffer de saida para a
 * janela deslizante quando há espaço na mesma.
 * Realiza também a verificação de timeouts
 * para possível reenvio.
 */
int windowActualSize = 0;
void *sendPackageThread(void* notUsed){

	window = new windowPackage*[WINDOW_SIZE]; //Criando a janela deslizante

	for(int i=0; i< WINDOW_SIZE; i++)
		window[i] = NULL; //Inicializando a janela



	while(true){

		//Tem que modificar --> se a logica for tirar do buffer e manter na janela
		while((windowBuffer.isEmpty()) && (windowActualSize == 0)){
			sleep(WAIT_WHILE_EMPTY);
		}

		list::windowNode *package = NULL;
		if(!windowBuffer.isEmpty())
			package = (list::windowNode*)windowBuffer.firstNode;


		struct timeval timeNow;

		//Preenche a janela com pacotes do buffer
		while(windowActualSize < WINDOW_SIZE && ((windowActualSize < steamControl) || windowActualSize==0) && !windowBuffer.isEmpty()){
			//if(windowActualSize == steamControl)
			//sleep(WAIT_WHILE_FULL);

			int i;
			for(i=0 ; i< WINDOW_SIZE ; i++) //Pega espaço vazio
				if(window[i]==NULL) break;
			if(i==WINDOW_SIZE) break;

			window[i] = (windowPackage*) (package->getData()); //Inclui pacote na janela
			windowActualSize++;

//			printf("Inclui pacote na janela \n");
//			printf("NOVO PACOTE...MsgId:   %d  BlockNumber:   %d   \n", window[i]->messageId, window[i]->blockNumber);


			//Necessário remover do buffer depois que está na janela(VER PROBLEMA logo abaixo)
			pthread_mutex_lock(&windMutBuffer);
			windowBuffer.removeFirstNode(); //Remove pacote do buffer
			pthread_mutex_unlock(&windMutBuffer);
			gettimeofday(&(window[i]->start), NULL);

			char* message;
			int messageSize = window[i]->mountPackageToSend(&message); //Monta mensagem


			TotalBlockSent++;
			TotalBytes += messageSize;
			int sent = tp_sendto(pjd_socket, message, messageSize, &other_end); //Envia pela primeira vez
			if(sent < 0)
				perror("Não enviou corretamente \n");


			if(window[i]->op==2){ //SUPONDO OP 2 == ACK... PACOTE NAO VAI FICAR NA JANELA... NEM NA LISTA.....
				window[i]=NULL;
				windowActualSize--;
			}

			if(!package->hasNext) break;
			package = package->next;
		}



//		printf("-- Indo para a fase de reenvio \n");
		//Retira pacotes confirmados, e re-envia quando há timeouts
		for(int i=0; i<WINDOW_SIZE; i++){
			if(window[i] != NULL){
				if(window[i]->ack){ //Esvaizando janela
//					printf("CONFIRMADO...MsgId:   %d  BlockNumber:   %d   \n", window[i]->messageId, window[i]->blockNumber);
					window[i]=NULL;
					windowActualSize--;
				}
				else{
					gettimeofday(&timeNow, NULL);
					double difference = (timeNow.tv_sec - window[i]->start.tv_sec) + (timeNow.tv_usec - window[i]->start.tv_usec)/1.e6;

					if(difference > TIME_TO_RESEND){ //Reenviando
//						printf("REENVIO...MsgId:   %d  BlockNumber:   %d   \n", window[i]->messageId, window[i]->blockNumber);
//						printf("-- Reenviando algum pacote da janela \n");

						gettimeofday(&(window[i]->start), NULL);
						char *message;
						int messageSize = window[i]->mountPackageToSend(&message);
						tp_sendto(pjd_socket, message, messageSize, &other_end);
						TotalBlockSent++;
						TotalBlockResent++;
						TotalBytes += messageSize;
					}
					else{
//						printf("TA NA JANELA...MsgId:   %d  BlockNumber:   %d   \n", window[i]->messageId, window[i]->blockNumber);
					}
				}
			}
		}
//		printf("-- Fim reenvio \n");
		//		if(windowActualSize == WINDOW_SIZE)
		//			sleep(WAIT_WHILE_FULL);
	}

}


pjdConnection::pjdConnection(){
	this->pjdId = ++nextPjdId;
	this->lastBlock=0;
	this->lastBlockAck=0;
	this->lastBlockSent=0;
}


pjdConnection::pjdConnection(int pjdId, int lastBlockSent, int lastBlockAck){
	this->pjdId = pjdId;
	this->lastBlockSent = lastBlockSent;
	this->lastBlockAck = lastBlockAck;
}

void pjdConnection::printNode(int number){
	printf("Node Number = %d,", number);
	printf("Received Node = %d, %d, %d\n",this->pjdId,this->lastBlockSent , this->lastBlockAck);
}

int pjdConnection::searchKey(){
	return this->pjdId;
}



/** Construtores **/

windowPackage::windowPackage(){

}

windowPackage::windowPackage(int op, int blockNumber, int dataLen, char* data){
	//CRC
	windowPackage::dataMaxSize = pjd_mtu();
	this->ack=false;
	this->blockNumber = blockNumber;
	this->op = op;
	this->dataLen = dataLen;

	if(dataLen>dataMaxSize){
		error("Data received is bigger than allowed. Critical bug.");
	}

	this-> data= new char[dataLen];
	for(int i=0; i<dataLen; i++){
		this->data[i] = data[i];
	}
	//this->data = data;

	//	printf("Construtor do windowPackage \n");

}


list::windowNode::windowNode(windowPackage data){
	this->data = data;
	next=NULL;
	previous=NULL;
	hasNext=false;
}


list::connectionNode::connectionNode(pjdConnection data){
	this->data = data;
	next=NULL;
	previous=NULL;
	hasNext=false;
}


/** Função auxiliar de impressão do nodo **/
void windowPackage::printNode(int number){
	printf("-- Imprimindo um windowPackage -- \n");
	printf("Node Number = %d,", number);
	printf("OP: %d, BlockNumber = %d, MessageNumber: %d, DataLen: %d, TotalBlocks = %d\n",this->op, this->blockNumber, this->messageId, this->dataLen, this->totalBlocks);
	//	if(this->op == 1){
	//		printf("Data = ");
	//		for(int i=0; i<this->dataLen; i++)
	//			std::cout << this->data[i]<< " : ";
	//		printf("\n");
	//	}
}

/**
 *  Montagem do pacote será feita pelo op definido
 *  no windowPackage. Observe que todos os pacotes possuem campos em comum
 *  de forma que eles foram setados de forma unica apos o switch;
 *
 **/
int windowPackage::mountPackageToSend(char** message){
	int size;
	char* package;


	//Varia de acordo com a data
	switch(op){
	case 1: /* Pacote de dados */

		size = 28 + dataLen;

		package = new char[size];

		//Data
		package[26] = totalBlocks/128;
		package[27] = totalBlocks % 128;
		package[28] = 30;
		for(int i = 28; i < size; i++)
			package[i] = data[i-28];

		break;

	case 2: /* ACK */

		/* tamanho: padrao + freeBuffer */
		size = 28;
		package = new char[size];

		package[26] = dataLen/128;
		package[27] = dataLen%128;

		break;

//	case 3: //Não foi utilizado para o protocolo
//
//		/* tamanho: Padrao + CodErr */
//		size = 28;
//		package = new char[size];
//
//		package[26] = 0;
//		package[27] = errCode;
//
//		break;

	case 4:
		size=28;
		package = new char[size];
		package[26] = totalBlocks/128;
		package[27] = totalBlocks % 128;
	}

	/* Campos em comum*/

	//OP
	package[0] = 0;
	package[1] = op;
	//BlockNumber
	package[2] = blockNumber/128;
	package[3] = blockNumber % 128;
	//MessageId
	package[4] = messageId/128;
	package[5] = messageId%128;

	for(int i = 6; i < 26; i++)
		package[i] = 0;

	//checkSum - Soma Byte a Byte
	int chkSum = 0;

	for(int i = 0; i < size; i++){
		chkSum += package[i];
	}

	//Escrevendo o checkSum em binário
	for(int i = 25; i > 5; i--){
		package[i] = chkSum % 128;
		chkSum /= 128;
	}

	*message = package;
	return size;
}


/** Verificação de bits, através do checkSum do pacote **/
bool windowPackage::verifyPackage(char* package, int size){
	int checkSumCalculated = 0;
	int checkSumOnPackage = 0;
	int expoente=0;

	//Primeiro gerando o int do checkSum contido no pacote
	for(int i = 25 ; i > 5; i--){
		checkSumOnPackage += (int)package[i]*(int)pow(128, expoente);
		expoente++;
	}

	//Encontrando o valor agora através da soma
	for(int i = 0; i < 6; i++){
		checkSumCalculated += package[i];
	}
	for(int i = 26; i < size; i ++){
		checkSumCalculated += package[i];
	}

	//printf("calc = %d, pack = %d\n", checkSumCalculated, checkSumOnPackage);
	if(checkSumCalculated ==  checkSumOnPackage)
		return true;
	else
		return false;
}


/**
 *  Faz o retorno do pacote para o formato windowPackage
 *  Também inclui os campos de acordo com o pacote recebido
 **/

windowPackage* windowPackage::mountPackageReceived(char *package, int size){
	windowPackage *newWindow = new windowPackage();
	int dataSize = size - 28;

	newWindow->op = package[1];
	newWindow->blockNumber = (package[2]*128)+package[3];
	newWindow->messageId = (package[4]*128)+package[5];
	//	newWindow->checkSum = (package[6]*128)+package[7]; //Nao precisa ter checkSum no windowPackage

	switch(newWindow->op){
	case 1: //Dados
		newWindow->totalBlocks = (package[26]*128) + package[27];
		newWindow->dataLen = dataSize;
		newWindow->data = new char[dataSize];

		for(int i = 0; i < dataSize	; i++)
			newWindow->data[i] = package[i+28];
		break;

	case 2: //ACK

		newWindow->dataLen = (package[26]*128)+package[27]; //BufferFree
		break;

//	case 3: //Error -- Nao utilizado
//		newWindow->errCode = (package[26]*128)+package[27];
//		break;

	case 4:
		newWindow->totalBlocks = (package[26]*128) + package[27];
		printf("\n\n\n\nEXIT!!!!!!!!!!!!!\n\n");
		break;
	}

	return newWindow;
}

/** Função auxiliar para a impressão do pacote na forma a ser enviada **/
void windowPackage::printPackageForm(){
	char* pack;

	int size = this->mountPackageToSend(&pack);

	printf("\n -- Impressão do windowPackage na forma de char* -- \n");
	for(int i=0; i< size; i++){
		printf("AD: %d  Valor: %c \n",i, pack[i]);
	}
}


windowPackage* list::windowNode::getData(){
	return &data;
}

pjdConnection* list::connectionNode::getData(){
	return &data;
}

const void* list::getFirstNode(){
	return firstNode;
}


int list::getSize(){
	return listSize;
}
bool list::isEmpty(){
	if(firstNode==NULL)
		return true;
	else
		return false;
}

bool list::isFull(){
	if(this->listSize == maxSize)
		return true;
	else
		return false;
}

bool list::exists(int key, int flag){
//	printf("Testando se existe key = %d, flag = %d\n", key, flag);
	windowNode* aux;
	aux=(windowNode*)firstNode;

	while(aux!=NULL) {
		if(aux->data.searchKey() == key){
//			printf("Achei pacote com mesma key, blkNum = %d\n", aux->data.blockNumber);
			if(aux->data.blockNumber==flag){
//				printf("Pacote Ja ExISTE -----------------------------------------\n");
				TotalBlockDuplicate++;
				return true;
			}
		}
		aux=aux->next;
	}
	return false;
}

/**
 * Função que remove determinado nodo da lista
 * a partir do key e do flag
 */
void list::deleteNode(int key, int flag){
//	printList();
	windowNode* aux;
	windowNode* aux1;
	windowNode* aux2;
	windowNode* first;
	aux=(windowNode*)firstNode;
	first = (windowNode*)firstNode;

	//int i;
//	printf("pretendo deletar node com flag = %d\n", flag);

	while(aux!=NULL){
		if(aux->data.searchKey() == key){
			if(flag > -1 && aux->data.blockNumber==flag){
//				printf("achei node flag maior\n");

				aux1=aux->previous;
				aux2=aux->next;
				if(aux == first){
					removeFirstNode();
					first = (windowNode*)firstNode;
					break;
				}
				else{
					if(aux2 != NULL){
						aux2->previous = aux1;
					}
					else{
						aux1->hasNext=false;
					}
					aux1->next=aux2;

					listSize--;
					break;
				}
			}
			else{

//				printf("achei node!!\n");

				aux1=aux->previous;
				aux2=aux->next;

				if(aux == first){
//					printf("era o primeiro\n");

					removeFirstNode();
					aux=(windowNode*) firstNode;
					first = (windowNode*)firstNode;
				}

				else{
//					printf("nao era o primeiro\n");

					aux=aux2;
					if(aux2 != NULL){
						aux2->previous = aux1;
					}
					else{ //Ultimo pacote
						aux1->hasNext=false;
						lastNode = aux1;
					}
					aux1->next=aux2;

					listSize--;
				}
			}
		}
		else
			aux=aux->next;
	}
//	printf("depois\n");
//	printList();
}


/**
 * Função que procura por um ou mais pacotes de acordo com
 * os parametros. Retorna um array de ponteiros caso todos
 * estes pacotes estejam presentes na lista. Caso contrário
 * retorna null.
 */
windowPackage** list::searchNode(int key, int flag){
	windowNode* aux;
	windowPackage** messageArray;
	int messageSize=1;;
	int arraySize=0;

	aux=(windowNode*)firstNode;
	//int i;
	while(aux!=NULL){
		if(aux->data.searchKey() == key){
			messageSize = aux->data.totalBlocks;
			messageArray = new windowPackage*[messageSize];
			for(int j=0; j< messageSize; j++)
				messageArray[j] = NULL;
			messageArray[aux->data.blockNumber] = &(aux->data);
			arraySize++;
			aux=aux->next;
			break;
		}
		aux=aux->next;
	}
	if(aux==NULL && arraySize < messageSize){ //Não foi encontrado todos os pacotes
//		printf("returning first null\n");

//		printf("size = %d\n", listSize);
		aux=(windowNode*)firstNode;
		while(aux!=NULL){
			//if(aux->data.searchKey() == key){
//			printf("found one:");
//			printf("msg = %d, Total = %d, this one = %d\n", aux->data.messageId, aux->data.totalBlocks, aux->data.blockNumber);

			aux=aux->next;
		}
		return NULL;
	}

	while(aux!=NULL){
		if(aux->data.searchKey() == key){
			if(messageArray[aux->data.blockNumber]==NULL){
				messageArray[aux->data.blockNumber] = &(aux->data);
				arraySize++;
			}
			else{
//				printf("n era para entrar aqui.. 10001\n");
			}
		}
		aux=aux->next;
	}

	if(arraySize == messageSize) //Encontrou todos
		return messageArray;

//	printf("aS = %d, mS = %d\n", arraySize, messageSize);
//	printf("returning null\n");

	return NULL;
}

pjdConnection* list::searchNode(int key){
	connectionNode* aux;
	aux=(connectionNode*)firstNode;
	while(aux!=NULL){
		if(aux->data.searchKey() == key){
			return &(aux->data);
		}
		aux=aux->next;
	}
	return NULL;
}

/** Construtores da lista **/
list::list(){
	maxSize=-1;
	firstNode=NULL;
	lastNode=NULL;
	listSize=0;
}

list::list(int maxSize){
	this->maxSize = maxSize;


	firstNode=NULL;
	lastNode=NULL;
	listSize=0;
}


/** Função de inserção de nodos na lista **/
bool list::insertNode(windowPackage newNodeData){
	if(maxSize>0 && listSize==maxSize)
		return false;

	windowNode *newNode = new windowNode(newNodeData);

	if(firstNode == NULL){
		newNode->previous = NULL;
		newNode->next = NULL;
		firstNode=newNode;
		lastNode=firstNode;
	}
	else{
		windowNode* aux = (windowNode*) lastNode;
		aux->next=newNode;
		aux->hasNext=true;

		//		lastNode->next=newNode;
		//		lastNode->hasNext=true;
		newNode->previous=aux;
		lastNode=newNode;

	}
	listSize++;
	return true;
}

bool list::insertNode(pjdConnection newNodeData){
	if(maxSize>0 && listSize==maxSize)
		return false;

	connectionNode *newNode = new connectionNode(newNodeData);

	if(firstNode == NULL){
		firstNode=newNode;
		lastNode=firstNode;
	}
	else{
		connectionNode* aux = (connectionNode*) lastNode;
		aux->next=newNode;
		aux->hasNext=true;
		//		lastNode->next=newNode;
		//		lastNode->hasNext=true;
		newNode->previous=aux;
		lastNode=newNode;
	}
	listSize++;
	return true;
}



bool list::removeFirstNode(){
	if(firstNode == NULL)
		return false;

	windowNode *aux=(windowNode*)firstNode;

	if(aux->hasNext){
		aux->next->previous=NULL;
		firstNode=aux->next;

		//		delete aux;
	}
	else{
		firstNode=NULL;
		lastNode=NULL;
		//		delete aux;
	}
	listSize--;
	return true;
}

bool list::removeFirstNode(int){
	if(firstNode == NULL)
		return false;

	connectionNode *aux=(connectionNode*)firstNode;

	if(aux->hasNext){
		firstNode=aux->next;
		//delete aux;
	}
	else{
		firstNode=NULL;
		lastNode=NULL;
		//delete aux;
	}
	listSize--;
	return true;
}



void list::printList(){
	printf("Print Function\n");
	windowNode* aux;
	aux=(windowNode*)firstNode;
	while (aux!=NULL){
		aux->data.printNode(1);
		aux=aux->next;
	}
}

void list::printList(int){
	printf("Print Function\n");
	connectionNode* aux;
	aux=(connectionNode*)firstNode;
	while (aux!=NULL){
		aux->data.printNode(1);
		aux=aux->next;
	}
}


int pjd_mtu(void)
{
	fprintf(stderr,"pjd_mtu\n");
	return tp_mtu();
}


int pjd_init( int pjd_port )
{

	struct sockaddr_in local_addr;
	int    addr_len = sizeof(local_addr);

	fprintf(stderr,"pjd_init\n");
	if (tp_init()<0) {
		fprintf(stderr,"pjd_init: erro em tp_init\n");
		return -1;
	}
	pjd_socket = tp_socket(pjd_port);

	if (pjd_port==0) {
		if (getsockname(pjd_socket,(struct sockaddr*)&local_addr,(socklen_t*)&addr_len)<0) {
			fprintf(stderr,"pjd_init: erro em getsockname\n");
			return -2;
		}
		pjd_port = ntohs(local_addr.sin_port);
	}



	return pjd_port;
}

int pjd_passivo( void )
{
	char   first_message[128];
	unsigned int    msg_len;

	fprintf(stderr,"pjd_passivo\n");
	msg_len = tp_recvfrom(pjd_socket,
			first_message, sizeof(first_message),
			&other_end);
	if (msg_len<(strlen(FIRST_MSG)+1)) {
		fprintf(stderr,"pjd_passivo: fist message tem tamanho errado (%u, devia ser '%8lx')\n", msg_len,strlen(FIRST_MSG)+1);
		return -1;
	}
	if (strncmp(first_message,FIRST_MSG,strlen(FIRST_MSG))) {
		fprintf(stderr,"pjd_passivo: esperava %s, recebeu %s\n",
				FIRST_MSG, first_message);
		return -2;
	}

	//Inicialização das threads
	int created = pthread_create(&sendThread, NULL, sendPackageThread, NULL);
	if(created != 0){
		perror("Erro ao criar a thread \n");
	}
	created = pthread_create(&receiveThread, NULL, receivePackageThread, NULL);
	if(created != 0)
		perror("Erro ao criar a thread \n");

	//Iniciando o tempo da transmissão
	time(&startTransferTime);

	return pjd_socket;
}

int pjd_ativo( char* other_host, int other_port )
{
	//unsigned int    msg_len;


	//activeConnections.
	fprintf(stderr,"pjd_ativo\n");
	if (tp_build_addr( &other_end, other_host, other_port )<0) {
		fprintf(stderr,"pjd_ativo: erro em tp_build_addr\n");
		return -1;
	}
	if (tp_sendto(pjd_socket,
			FIRST_MSG, strlen(FIRST_MSG)+1,
			&other_end)<0) {
		fprintf(stderr,"pjd_ativo: erro em tp_sendto\n");
		return -2;
	}
	active_end = 1;

	//Inicialização das threads
	int created = pthread_create(&sendThread, NULL, sendPackageThread, NULL);
	if(created != 0){
		perror("Erro ao criar a thread \n");
	}
	created = pthread_create(&receiveThread, NULL, receivePackageThread, NULL);
	if(created != 0)
		perror("Erro ao criar a thread \n");

	//Iniciando o tempo da transmissão
	time(&startTransferTime);

	return pjd_socket;
}


/**
 * Função que divide a mensagem recebida de acordo com o
 * tamanho estabelecido. Cada chamada, retorna o bloco
 * correspondente
 */
char& divideMsg(char* msg, int msgLen, int& dataLen){

	char* package;
	static int index=0;
	if(index >= msgLen){
		package=NULL;
		dataLen = 0;
		index=0;
		return *package;
	}

	int packageSize=0;
	int packageMaxSize = pjd_mtu();
	if((msgLen - index) >= packageMaxSize)
		packageSize=packageMaxSize;
	else
		packageSize = (msgLen - index);

	package = new char [packageSize];

	int i=0;
	while(i< packageSize) {
		package[i] = msg[i+index];
		i++;
	}
	index+=i;


	dataLen = packageSize;
	return *package;
}


int pjd_send(int pjdd, char* msg_buffer, int msg_len)
{

	int blkNumber = 0;

	int dataLen=0;

	if(msg_len > MAX_PJD_MSG){
		error("Message received is bigger than allowed by PJD.");
	}

	char * dataBuffer = &divideMsg(msg_buffer, msg_len, dataLen);

	while(dataLen != 0){

		windowPackage* package= new windowPackage(1,blkNumber,dataLen,dataBuffer); //Montando pacote de dados
		package->messageId = messageNumber;
		package->totalBlocks = (msg_len / pjd_mtu());
		if(msg_len % pjd_mtu() > 0)
			package->totalBlocks++;
//		printf("Data len retornada por metodo = %d, msg len allowed = %d\n", dataLen, msg_len);
//		printf("Vezes passadas aqui por mensagem = %d , = %d\n", messageNumber, blkNumber);
//		printf("Total blocks = %d\n", package->totalBlocks);

		//package->printNode(1);

		//Verify
		while(windowBuffer.isFull()){
			sleep(WAIT_WHILE_FULL);
		}

		pthread_mutex_lock(&windMutBuffer);
		windowBuffer.insertNode(*package); //Inserindo o pacote no buffer de saida
		pthread_mutex_unlock(&windMutBuffer);
		blkNumber++;

		dataBuffer = &divideMsg(msg_buffer, msg_len, dataLen);

	}
	messageNumber++;
	if(messageNumber == 128*128) //Tratando os limites do valor do Id das mensagens
		messageNumber=1;

	return 0;
}


int pjd_recv(int pjdd, char* msg_buffer, int msg_len)
{

//	printf("-- pjdrecv\n");

	int messageSize;
	int totalSize;
	int dataLen;
	int blocksSent=0;
	int offset=0;
	windowPackage** message;

	static int sentBytes=0;
	while(true){

		while(windowRecvBuffer.isEmpty()){ //Espera quando não há nada a receber
			sleep(WAIT_WHILE_EMPTY);
		}

		message = windowRecvBuffer.searchNode(messageRecvdNumber, 0); //Faz uma procura por toda a mensagem que o receive deseja obter
		if(message != NULL){
//			printf("-- Achei a mensagem!!\n");

			if(message[0]->op==4){ //Mensagem de finalização da transmissão
//				while((!windowBuffer.isEmpty()) || (!windowIsEmpty())){
//					sleep(WAIT_WHILE_EMPTY);
//				}
				return 0;
			}

			messageSize = message[0]->totalBlocks;
			dataLen = message[0] -> dataLen;
			totalSize = (messageSize-1)*dataLen + message[messageSize-1]->dataLen;
			//if(msg_len < (totalSize - sentBytes)){
			blocksSent = sentBytes / dataLen;
			offset = sentBytes % dataLen;
			//			blocksSent++;
			//}

			int i = 0;
//			printf("-- Montando char de retorno!!\n");
			while(i < msg_len){
				sentBytes++;
				msg_buffer[i++] = message[blocksSent]->data[offset++];

				if(offset == message[blocksSent]->dataLen){ //Chegou ao final de um pacote

					if(blocksSent == messageSize-1){ //Chegou ao final da mensagem

						sentBytes=0;
						pthread_mutex_lock(&recvMutBuffer);
						windowRecvBuffer.deleteNode(messageRecvdNumber, -1);
						pthread_mutex_unlock(&recvMutBuffer);

						windowPackage *ackPackage = new windowPackage(); //Confirmação do final da transmissão
						ackPackage->op = 2;
						ackPackage->blockNumber = -1;
						ackPackage->messageId = -1;
						ackPackage->dataLen = BUFFER_SIZE - windowRecvBuffer.getSize(); // Manda para o receptor a quantidade restante de buffer
						pthread_mutex_lock(&windMutBuffer);
						windowBuffer.insertNode(*ackPackage);
						pthread_mutex_unlock(&windMutBuffer);


						messageRecvdNumber++;
						if(messageRecvdNumber == 128*128)
							messageRecvdNumber=1;
						break;
					}
					offset=0;
					blocksSent++;
				}
			}
			return i;
		}
		else{
			sleep(WAIT_RECV); //Espera pois a mensagem ainda não está completa no buffer
		}

	}
}


int pjd_close( int pjdd )
{

	//Só termina a conexão após o envio de todos os pacotes
	while((!windowBuffer.isEmpty()) || (!windowIsEmpty())){
		sleep(WAIT_WHILE_EMPTY);
	}


	//printf("Cheguei no close \n");
	fprintf(stderr,"pjd_close\n");
	if (active_end) { // Envolve apenas o processo ativo --> envio do pacote final

		windowPackage* package= new windowPackage();
		package->messageId = messageNumber;
		package->totalBlocks = 1;
		package->op=4;
		package->dataLen=0;
		package->ack=false;

		pthread_mutex_lock(&windMutBuffer);
		windowBuffer.insertNode(*package);
		pthread_mutex_unlock(&windMutBuffer);

		while((!windowBuffer.isEmpty()) || (!windowIsEmpty())){
			sleep(WAIT_WHILE_EMPTY);
		}
	}

	//Finalizando o tempo da transmissão
	time(&endTransferTime);
	TransferResume(); //Imprimindo dados da transferência

	return 0;
}
